/**
 *  @author Morozan Ion
 *  
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.dddforandroid.crud;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;

import com.dddforandroid.c2dm.MessageUtil;
import com.dddforandroid.classes.DeviceInfo;
import com.dddforandroid.util.Util;
import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Transaction;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

@Path("/v2")
public class RESTfulGeneric {

	private final String ID = "id";
	private String kind = null;
	private ArrayList<String> property = new ArrayList<String>();

	/********************************************************************************************************************
	 * 														GET		 													*
	 ********************************************************************************************************************/
	/**
	 * For testing the c2dm protocol! it sends an "hello world message"
	 * to registered devices.
	 * 
	 * in browser: http://localhost:8888/api/v2/c2dm
	 * 
	 * @return - specified data
	 * @throws EntityNotFoundException
	 */
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/c2dm")
	public Response c2dmAuth() {

		ResponseBuilder builder = Response.status(Response.Status.OK);

		/** Get information about current logged in user id */
		Util user = new Util();
		
		/** Set the current namespace to Admin */
		NamespaceManager.set(Util.ADMIN_NAMESPACE);
		
		/** Get a handle on the datastore itself */
		PersistenceManager db = PMF.get().getPersistenceManager();
		
		String registrationid = null;
		/** get the registration id */
		try{
			registrationid = db.getObjectById(DeviceInfo.class, user.getUserEmail()).getRegistrationid();
		}catch (Exception e) {
			builder = Response.status(Response.Status.NOT_FOUND).entity(
					"Registration id not found! Did you register? ");
			return builder.build();
		}
		
		int responseCode = 0;
		try {
			responseCode = MessageUtil.sendMessage(Util.getToken(),	registrationid,
													"Hello World!");
			if(responseCode != 200){
				builder = Response.status(Response.Status.SERVICE_UNAVAILABLE).entity(
						"Error while sending the message! Response code: " + responseCode);
				return builder.build();
			}
				
		} catch (IOException e) {
			e.printStackTrace();
		}
		builder = Response.status(Response.Status.OK).entity(
				"Message successufully send!");
		return builder.build();
	}

	/**
	 * in browser: http://localhost:8888/api/v2/get/123?dbName=Name
	 * 
	 * @param id
	 *            - the id of data
	 * @param dbName
	 *            - the name of the Database
	 * @return - specified data
	 * @throws EntityNotFoundException
	 */
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/get/{id}")
	public Response getDataById(@PathParam("id") String id,
			@QueryParam("dbName") String dbName) throws EntityNotFoundException {

		ResponseBuilder builder = Response.status(Response.Status.OK);

		if (null == dbName || dbName.isEmpty()) {
			builder = Response.status(Response.Status.BAD_REQUEST).entity(
					"Unreconized Database! Did you specify the name of it?");
			return builder.build();
		}

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/** Get a handle on the datastore itself */
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();

		/* keep database consistent */
		Transaction transaction = datastore.beginTransaction();
		try {
			try {
				Entity en = datastore.get(KeyFactory.createKey(dbName, id));
				builder.entity(en.getProperties());
				transaction.commit();
			} catch (Exception e) {
				Logger.getLogger(e.getMessage());
			}
		} finally {
			/* if smth went wrong */
			if (transaction.isActive()) {
				transaction.rollback();
				builder = Response.status(Response.Status.NOT_FOUND).entity(
						"Key or Database schema not found! Check again!");
			}
		}
		return builder.build();
	}

	/**
	 * in browser: http://localhost:8888/api/v2/get?dbName=Name
	 * 
	 * @return JSON File with User's database
	 */
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/get")
	public Response getAllData(@QueryParam("dbName") String dbName) {

		ResponseBuilder builder = Response.status(Response.Status.OK);

		if (null == dbName || dbName.isEmpty()) {
			builder = Response.status(Response.Status.BAD_REQUEST).entity(
					"Unreconized Database! Did you specify the name of it?");
			return builder.build();
		}

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/** Get a handle on the datastore itself */
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();

		/** making a list with all the entites from DB */
		Query query = new Query(dbName);
		List<Entity> Data = datastore.prepare(query).asList(
				FetchOptions.Builder.withDefaults());
		/** Extract only the properties(Actual data) */
		/* Represents a response entity of a generic type T. */
		Vector<Map<String, Object>> Properties = new Vector<Map<String, Object>>();
		for (Entity en : Data) {
			Properties.add(en.getProperties());
		}

		try {
			/* if there is no data in DB -> Error message */
			if (!Properties.isEmpty()) {
				builder.entity(Properties);
			} else {
				builder = Response.status(Response.Status.NOT_FOUND).entity(
						"No data available for given Database!");
			}
		} catch (Exception e) {
			Logger.getLogger(e.getMessage());
		}

		return builder.build();
	}

	/********************************************************************************************************************
	 * 														PUT	 														*
	 ********************************************************************************************************************/
	
	/**
	 * curl example: curl -v http://localhost:8888/api/v1/note/update/123 -X PUT
	 * -d @test.json -H "Content-Type: application/json"
	 * 
	 * @param note
	 *            - one entry from user's database
	 * @param id
	 *            - the uuid that has to match with the one from the note
	 * @throws EntityNotFoundException
	 * 
	 */
	@PUT
	@Consumes(MediaType.APPLICATION_JSON)
	@Path("/update/{dbName}/{id}")
	public Response Update(String row, @PathParam("id") String id,
			@PathParam("dbName") String dbName) throws EntityNotFoundException {

		ResponseBuilder builder = Response.status(Response.Status.OK);

		if (null == row || row.isEmpty()) {
			return builder.build();
		}

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/** Get a handle on the datastore itself */
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();

		/* keep database consistent */
		Transaction transaction = datastore.beginTransaction();

		final JsonParser parser = new JsonParser();
		JsonElement jsonEntry = null;
		Object properties[] = null;
		try {
			jsonEntry = parser.parse(row);
			properties = getProperties(dbName, datastore, user.getUserId());
			if (null == properties) {
				builder = Response.status(Response.Status.BAD_REQUEST).entity(
						"Db does not exist!");
				return builder.build();
			}
			transaction.commit();

			Entity entry = new Entity(dbName, id);
			/** add properties to entity */
			for (int i = 0; i < properties.length; i++) {
				String property = properties[i].toString();
				Object value = jsonEntry.getAsJsonObject().get(property)
						.getAsString();
				entry.setProperty(property, value);
			}
			/** add each entity to database in a consistent way */
			transaction = datastore.beginTransaction();
			datastore.put(entry);
			transaction.commit();
		} finally {
			/* if smth went wrong */
			if (transaction.isActive()) {
				transaction.rollback();
				builder = Response.status(Response.Status.NOT_FOUND).entity(
						"Key or Database schema not found! Check again!");
				return builder.build();
			}
		}
		builder = Response.status(Response.Status.OK).entity(
				"Database successfully updated!");
		return builder.build();
	}
	
	/********************************************************************************************************************
	 * 														DELETE	 													*
	 ********************************************************************************************************************/

	/**
	 * curl example: curl -v http://localhost:8888/api/v1/delete/{dbName}/{id}
	 * -X DELETE -d @test.json -H "Content-Type: application/json"
	 * 
	 * @param Table
	 *            - User database
	 * @return Message to inform the client if transaction went well or not
	 */
	@DELETE
	@Consumes(MediaType.APPLICATION_JSON)
	@Path("/delete/{dbName}/{id}")
	public Response Delete(@PathParam("id") String id,
			@PathParam("dbName") String dbName) {

		ResponseBuilder builder = Response.status(Response.Status.OK);

		if (null == dbName || dbName.isEmpty()) {
			builder = Response.status(Response.Status.BAD_REQUEST).entity(
					"Unreconized Database! Did you specify the name of it?");
			return builder.build();
		}

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/** Get a handle on the datastore itself */
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();

		/* keep database consistent */
		Transaction transaction = datastore.beginTransaction();
		try {
			try {
				datastore.delete(KeyFactory.createKey(dbName, id));
				transaction.commit();
			} catch (Exception e) {
				Logger.getLogger(e.getMessage());
			}
		} finally {
			/* if smth went wrong */
			if (transaction.isActive()) {
				transaction.rollback();
				builder = Response.status(Response.Status.NOT_FOUND).entity(
						"Key or Database schema not found! Check again!");
				return builder.build();
			}
		}
		/** if everything went right -> Send response */
		builder = Response.status(Response.Status.OK).entity(
				"Item successfully deleted!");
		return builder.build();
	}

	/********************************************************************************************************************
	 * 														POST	 													*
	 ********************************************************************************************************************/

	/**
	 * curl example: curl -v http://localhost:8888/api/v1/create/{dbName} -X
	 * POST -d
	 * 
	 * @test.json -H "Content-Type: application/json"
	 * 
	 * @param Table
	 *            - User database
	 * @return Message to inform the client if transaction went well or not
	 */
	@POST
	@Consumes(MediaType.APPLICATION_JSON)
	@Path("/create/{dbName}")
	public Response Add(String Table, @PathParam("dbName") String dbName) {

		ResponseBuilder builder = Response.status(Response.Status.OK);

		if (null == Table || Table.isEmpty()) {
			return builder.build();
		}

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/** Get a handle on the datastore itself */
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();

		/** keep database consistent */
		Transaction transaction = datastore.beginTransaction();

		/**
		 * Parse the entries and create the entities based on specific database
		 */
		final JsonParser parser = new JsonParser();
		JsonElement jsonElement = null;
		Object properties[] = null;
		try {
			jsonElement = parser.parse(Table);
			properties = getProperties(dbName, datastore, user.getUserId());
			if (null == properties) {
				builder = Response.status(Response.Status.BAD_REQUEST).entity(
						"Db does not exist!");
				return builder.build();
			}
			/** get the entries */
			final JsonObject jsonObject = jsonElement.getAsJsonObject();
			JsonArray table = jsonObject.get(dbName).getAsJsonArray();
			transaction.commit();

			Entity entry = null;
			for (JsonElement obj : table) {
				/** get the unique identifier */
				String key = obj.getAsJsonObject().get(ID).toString()
						.replaceAll("\"", "");
				System.out.println(key);
				entry = new Entity(dbName, key);
				/** add properties to entity */
				for (int i = 0; i < properties.length; i++) {
					String property = properties[i].toString();
					Object value = obj.getAsJsonObject().get(property)
							.getAsString();
					entry.setProperty(property, value);
				}
				/** add each entity to database in a consistent way */
				transaction = datastore.beginTransaction();
				datastore.put(entry);
				transaction.commit();
			}

		} finally {
			/* if smth went wrong */
			if (transaction.isActive()) {
				transaction.rollback();
				builder = Response.status(Response.Status.BAD_REQUEST).entity(
						"Transaction Failed! Is Json file correct? ");
				return builder.build();
			}
		}

		builder = Response.status(Response.Status.OK).entity(
				"Database succesfully updated!");
		return builder.build();
	}

	/**
	 * Extract the properties of the database
	 * 
	 * @param dbName
	 *            - name of the Database
	 * @param datastore
	 *            - handler to datastore
	 * @return intrinsec vector with properties(fields) of the database
	 */
	private Object[] getProperties(String dbName, DatastoreService datastore,
			String userID) {
		Entity database;
		Object properties[] = null;

		/** get the schema of the database */
		try {
			database = datastore.get(KeyFactory.createKey(dbName, userID));
			/** Extract all the properties of the Database */
			properties = database.getProperties().keySet().toArray();
		} catch (EntityNotFoundException e) {
			Logger.getLogger(e.getMessage());
		}

		return properties;
	}

	/**
	 * curl example:curl example: http://localhost:8888/api/v2/initialize -X
	 * POST -d @class.json -H "Content-Type: application/json"
	 * 
	 * @param DatabaseSchema
	 *            - Schema of the database
	 */
	@POST
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/initialize")
	public Response initialize(String DatabaseSchema) {
		ResponseBuilder builder = Response.status(Response.Status.OK);

		/** Get information about current logged in user id */
		Util user = new Util();
		/** Create a namespace for current loggedin user. */
		NamespaceManager.set(user.getUserId());

		/** Get a handle on the datastore itself */
		DatastoreService datastore = DatastoreServiceFactory
				.getDatastoreService();
		
		try {
			/** Extract fields to create Database Schema */
			StringToJson(DatabaseSchema);
		} catch (Exception e) {
			builder = Response.status(Response.Status.BAD_REQUEST).entity(
					"Malformed Json file!");
			return builder.build();
		}

		/**
		 * Create Kind(Name of the Database) & Key(UUID) and add
		 * properties(colomns)
		 * */
		Entity dbSchema = new Entity(this.kind, user.getUserId());

		for (String property : this.property) {
			dbSchema.setProperty(property, null);
		}
		datastore.put(dbSchema);

		builder = Response.status(Response.Status.OK).entity(
				"Your schema was succesfully created!");
		/* Send response */
		return builder.build();
	}

	/**
	 * curl example:curl http://localhost:8888/api/v2/initialize -X
	 * POST -d @class.json -H "Content-Type: application/json"
	 * 
	 * @param DatabaseSchema
	 *            - Schema of the database
	 */
	@POST
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/register")
	public Response RegisterToC2dm(String RegInfo) {
		ResponseBuilder builder = Response.status(Response.Status.OK);
		
		if (null == RegInfo || RegInfo.isEmpty()) {
			builder = Response.status(Response.Status.NOT_FOUND).entity(
					"Registration info not found!");
			return builder.build();
		}
		
		/** Get information about current logged in user id */
		Util user = new Util();
		
		/** Set the current namespace to Admin */
		NamespaceManager.set(Util.ADMIN_NAMESPACE);

		/** Get a handle on the datastore itself */
		PersistenceManager db = PMF.get().getPersistenceManager();
		
		/**
		 * Parse the entries and create the entities based on specific database
		 */
		final JsonParser parser = new JsonParser();
		JsonElement jsonElement = null;
		try {
			jsonElement = parser.parse(RegInfo);
			/** get deviceid and registrationid */
			final JsonObject jsonObject = jsonElement.getAsJsonObject();
			String deviceid = jsonObject.get(Util.DEVICE_ID).toString().replaceAll("\"", "");
			String registrationid = jsonObject.get(Util.REGSITRATION_ID).toString().replaceAll("\"", "");
			
			/** Create a new deviceInfo and add to db */
			DeviceInfo devInfo = new DeviceInfo(deviceid, registrationid);
			devInfo.setId(user.getUserEmail());
			try{
				db.makePersistent(devInfo);
			}finally{
				db.close();
			}
			
		}catch (Exception e) {
			Logger.getLogger(e.getMessage());
		}
		
		builder = Response.status(Response.Status.OK).entity(
				"Successfully registered!");
		/* Send response */
		return builder.build();
	}

	/**
	 * Extract the fields(properties) of a new database
	 * @param jsonString - string to parse
	 */
	protected void StringToJson(String jsonString) {
		final JsonParser parser = new JsonParser();
		final JsonElement jsonElement = parser.parse(jsonString);
		final JsonObject jsonObject = jsonElement.getAsJsonObject();

		/** kind == the Name of the Class */
		this.kind = jsonObject.get("kind").getAsString();
		/** property == fileds from the class */
		JsonArray property = jsonObject.get("property").getAsJsonArray();
		for (int i = 0; i < property.size(); i++) {
			this.property.add(property.get(i).getAsString());
		}

		return;
	}
}